list.c revision 1.19
1/* $NetBSD: list.c,v 1.19 2006/12/25 18:43:29 christos Exp $ */ 2 3/* 4 * Copyright (c) 1980, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32#include <sys/cdefs.h> 33#ifndef lint 34#if 0 35static char sccsid[] = "@(#)list.c 8.4 (Berkeley) 5/1/95"; 36#else 37__RCSID("$NetBSD: list.c,v 1.19 2006/12/25 18:43:29 christos Exp $"); 38#endif 39#endif /* not lint */ 40 41#include <assert.h> 42#include <regex.h> 43#include <util.h> 44 45#include "rcv.h" 46#include "extern.h" 47#include "format.h" 48#include "thread.h" 49#include "mime.h" 50 51/* 52 * Mail -- a mail program 53 * 54 * Message list handling. 55 */ 56 57/* 58 * Token values returned by the scanner used for argument lists. 59 * Also, sizes of scanner-related things. 60 */ 61enum token_e { 62 TEOL, /* End of the command line */ 63 TNUMBER, /* A message number or range of numbers */ 64 TDASH, /* A simple dash */ 65 TSTRING, /* A string (possibly containing '-') */ 66 TDOT, /* A "." */ 67 TUP, /* An "^" */ 68 TDOLLAR, /* A "$" */ 69 TSTAR, /* A "*" */ 70 TOPEN, /* An '(' */ 71 TCLOSE, /* A ')' */ 72 TPLUS, /* A '+' */ 73 TAND, /* A '&' */ 74 TOR, /* A '|' */ 75 TXOR, /* A logical '^' */ 76 TNOT, /* A '!' */ 77 TERROR /* A lexical error */ 78}; 79 80#define REGDEP 2 /* Maximum regret depth. */ 81#define STRINGLEN 1024 /* Maximum length of string token */ 82 83static int lexnumber; /* Number of TNUMBER from scan() */ 84static char lexstring[STRINGLEN]; /* String from TSTRING, scan() */ 85static int regretp; /* Pointer to TOS of regret tokens */ 86static int regretstack[REGDEP]; /* Stack of regretted tokens */ 87static char *string_stack[REGDEP]; /* Stack of regretted strings */ 88static int numberstack[REGDEP]; /* Stack of regretted numbers */ 89 90/* 91 * Scan out the list of string arguments, shell style 92 * for a RAWLIST. 93 */ 94PUBLIC int 95getrawlist(const char line[], char **argv, int argc) 96{ 97 char c, *cp2, quotec; 98 const char *cp; 99 int argn; 100 char linebuf[LINESIZE]; 101 102 argn = 0; 103 cp = line; 104 for (;;) { 105 for (; *cp == ' ' || *cp == '\t'; cp++) 106 continue; 107 if (*cp == '\0') 108 break; 109 if (argn >= argc - 1) { 110 (void)printf( 111 "Too many elements in the list; excess discarded.\n"); 112 break; 113 } 114 cp2 = linebuf; 115 quotec = '\0'; 116 while ((c = *cp) != '\0') { 117 cp++; 118 if (quotec != '\0') { 119 if (c == quotec) 120 quotec = '\0'; 121 else if (c == '\\') 122 switch (c = *cp++) { 123 case '\0': 124 *cp2++ = '\\'; 125 cp--; 126 break; 127 case '0': case '1': case '2': case '3': 128 case '4': case '5': case '6': case '7': 129 c -= '0'; 130 if (*cp >= '0' && *cp <= '7') 131 c = c * 8 + *cp++ - '0'; 132 if (*cp >= '0' && *cp <= '7') 133 c = c * 8 + *cp++ - '0'; 134 *cp2++ = c; 135 break; 136 case 'b': 137 *cp2++ = '\b'; 138 break; 139 case 'f': 140 *cp2++ = '\f'; 141 break; 142 case 'n': 143 *cp2++ = '\n'; 144 break; 145 case 'r': 146 *cp2++ = '\r'; 147 break; 148 case 't': 149 *cp2++ = '\t'; 150 break; 151 case 'v': 152 *cp2++ = '\v'; 153 break; 154 default: 155 *cp2++ = c; 156 } 157 else if (c == '^') { 158 c = *cp++; 159 if (c == '?') 160 *cp2++ = '\177'; 161 /* null doesn't show up anyway */ 162 else if ((c >= 'A' && c <= '_') || 163 (c >= 'a' && c <= 'z')) 164 *cp2++ = c & 037; 165 else { 166 *cp2++ = '^'; 167 cp--; 168 } 169 } else 170 *cp2++ = c; 171 } else if (c == '"' || c == '\'') 172 quotec = c; 173 else if (c == ' ' || c == '\t') 174 break; 175 else 176 *cp2++ = c; 177 } 178 *cp2 = '\0'; 179 argv[argn++] = savestr(linebuf); 180 } 181 argv[argn] = NULL; 182 return argn; 183} 184 185/* 186 * Mark all messages that the user wanted from the command 187 * line in the message structure. Return 0 on success, -1 188 * on error. 189 */ 190 191/* 192 * Bit values for colon modifiers. 193 */ 194#define CMBOX 0x001 /* Unread messages */ 195#define CMDELETED 0x002 /* Deleted messages */ 196#define CMMODIFY 0x004 /* Unread messages */ 197#define CMNEW 0x008 /* New messages */ 198#define CMOLD 0x010 /* Old messages */ 199#define CMPRESERVE 0x020 /* Unread messages */ 200#define CMREAD 0x040 /* Read messages */ 201#define CMSAVED 0x080 /* Saved messages */ 202#define CMTAGGED 0x100 /* Tagged messages */ 203#define CMUNREAD 0x200 /* Unread messages */ 204#define CMNEGATE 0x400 /* Negate the match */ 205#define CMMASK 0x7ff /* Mask the valid bits */ 206 207/* 208 * The following table describes the letters which can follow 209 * the colon and gives the corresponding modifier bit. 210 */ 211 212static const struct coltab { 213 char co_char; /* What to find past : */ 214 int co_bit; /* Associated modifier bit */ 215 int co_mask; /* m_status bits to mask */ 216 int co_equal; /* ... must equal this */ 217} coltab[] = { 218 { '!', CMNEGATE, 0, 0 }, 219 { 'd', CMDELETED, MDELETED, MDELETED }, 220 { 'e', CMMODIFY, MMODIFY, MMODIFY }, 221 { 'm', CMBOX, MBOX, MBOX }, 222 { 'n', CMNEW, MNEW, MNEW }, 223 { 'o', CMOLD, MNEW, 0 }, 224 { 'p', CMPRESERVE, MPRESERVE, MPRESERVE }, 225 { 'r', CMREAD, MREAD, MREAD }, 226 { 's', CMSAVED, MSAVED, MSAVED }, 227 { 't', CMTAGGED, MTAGGED, MTAGGED }, 228 { 'u', CMUNREAD, MREAD|MNEW, 0 }, 229 { 0, 0, 0, 0 } 230}; 231 232static int lastcolmod; 233 234static int 235ignore_message(int m_flag, int colmod) 236{ 237 int ignore_msg; 238 const struct coltab *colp; 239 240 ignore_msg = !(colmod & CMNEGATE); 241 colmod &= (~CMNEGATE & CMMASK); 242 243 for (colp = &coltab[0]; colp->co_char; colp++) 244 if (colp->co_bit & colmod && 245 (m_flag & colp->co_mask) == colp->co_equal) 246 return !ignore_msg; 247 return ignore_msg; 248} 249 250/* 251 * Turn the character after a colon modifier into a bit 252 * value. 253 */ 254static int 255evalcol(int col) 256{ 257 const struct coltab *colp; 258 259 if (col == 0) 260 return lastcolmod; 261 for (colp = &coltab[0]; colp->co_char; colp++) 262 if (colp->co_char == col) 263 return colp->co_bit; 264 return 0; 265} 266 267static int 268get_colmod(int colmod, char *cp) 269{ 270 if ((cp[0] == '\0') || 271 (cp[0] == '!' && cp[1] == '\0')) 272 colmod |= lastcolmod; 273 274 for (/*EMPTY*/; *cp; cp++) { 275 int colresult; 276 if ((colresult = evalcol(*cp)) == 0) { 277 (void)printf("Unknown colon modifier \"%s\"\n", lexstring); 278 return -1; 279 } 280 if (colresult == CMNEGATE) 281 colmod ^= CMNEGATE; 282 else 283 colmod |= colresult; 284 } 285 return colmod; 286} 287 288static int 289syntax_error(const char *msg) 290{ 291 (void)printf("Syntax error: %s\n", msg); 292 return -1; 293} 294 295/* 296 * scan out a single lexical item and return its token number, 297 * updating the string pointer passed **p. Also, store the value 298 * of the number or string scanned in lexnumber or lexstring as 299 * appropriate. In any event, store the scanned `thing' in lexstring. 300 */ 301static enum token_e 302scan(char **sp) 303{ 304 static const struct lex { 305 char l_char; 306 enum token_e l_token; 307 } singles[] = { 308 { '$', TDOLLAR }, 309 { '.', TDOT }, 310 { '^', TUP }, 311 { '*', TSTAR }, 312 { '-', TDASH }, 313 { '+', TPLUS }, 314 { '(', TOPEN }, 315 { ')', TCLOSE }, 316 { '&', TAND }, 317 { '|', TOR }, 318 { '!', TNOT }, 319 { 0, 0 } 320 }; 321 const struct lex *lp; 322 char *cp, *cp2; 323 int c; 324 int quotec; 325 326 if (regretp >= 0) { 327 (void)strcpy(lexstring, string_stack[regretp]); 328 lexnumber = numberstack[regretp]; 329 return regretstack[regretp--]; 330 } 331 cp = *sp; 332 cp2 = lexstring; 333 lexstring[0] = '\0'; 334 335 /* 336 * strip away leading white space. 337 */ 338 cp = skip_blank(cp); 339 c = *cp++; 340 341 /* 342 * If no characters remain, we are at end of line, 343 * so report that. 344 */ 345 if (c == '\0') { 346 *sp = --cp; 347 return TEOL; 348 } 349 350 /* 351 * If the leading character is a digit, scan 352 * the number and convert it on the fly. 353 * Return TNUMBER when done. 354 */ 355 if (isdigit(c)) { 356 lexnumber = 0; 357 while (isdigit(c)) { 358 lexnumber = lexnumber * 10 + c - '0'; 359 *cp2++ = c; 360 c = *cp++; 361 } 362 *cp2 = '\0'; 363 *sp = --cp; 364 return TNUMBER; 365 } 366 367 /* 368 * Check for single character tokens; return such 369 * if found. 370 */ 371 for (lp = &singles[0]; lp->l_char != 0; lp++) 372 if (c == lp->l_char) { 373 lexstring[0] = c; 374 lexstring[1] = '\0'; 375 *sp = cp; 376 return lp->l_token; 377 } 378 379 /* 380 * We've got a string! Copy all the characters 381 * of the string into lexstring, until we see 382 * a null, space, or tab. 383 * Respect quoting and quoted pairs. 384 */ 385 quotec = 0; 386 while (c != '\0') { 387 if (c == quotec) { 388 quotec = 0; 389 c = *cp++; 390 continue; 391 } 392 if (quotec) { 393 if (c == '\\' && (*cp == quotec || *cp == '\\')) 394 c = *cp++; 395 } 396 else { 397 switch (c) { 398 case '\'': 399 case '"': 400 quotec = c; 401 c = *cp++; 402 continue; 403 case ' ': 404 case '\t': 405 c = '\0'; /* end of token! */ 406 continue; 407 default: 408 break; 409 } 410 } 411 if (cp2 - lexstring < STRINGLEN - 1) 412 *cp2++ = c; 413 c = *cp++; 414 } 415 if (quotec && c == 0) { 416 (void)fprintf(stderr, "Missing %c\n", quotec); 417 return TERROR; 418 } 419 *sp = --cp; 420 *cp2 = '\0'; 421 return TSTRING; 422} 423 424/* 425 * Unscan the named token by pushing it onto the regret stack. 426 */ 427static void 428regret(int token) 429{ 430 if (++regretp >= REGDEP) 431 errx(1, "Too many regrets"); 432 regretstack[regretp] = token; 433 lexstring[sizeof(lexstring) - 1] = '\0'; 434 string_stack[regretp] = savestr(lexstring); 435 numberstack[regretp] = lexnumber; 436} 437 438/* 439 * Reset all the scanner global variables. 440 */ 441static void 442scaninit(void) 443{ 444 regretp = -1; 445} 446 447#define DELIM " \t," /* list of string delimiters */ 448static int 449is_substr(const char *big, const char *little) 450{ 451 const char *cp; 452 if ((cp = strstr(big, little)) == NULL) 453 return 0; 454 455 return strchr(DELIM, cp[strlen(little)]) != 0 && 456 (cp == big || strchr(DELIM, cp[-1]) != 0); 457} 458#undef DELIM 459 460 461/* 462 * Look for (compiled regex) pattern in a line. 463 * Returns: 464 * 1 if match found. 465 * 0 if no match found. 466 * -1 on error 467 */ 468static int 469regexcmp(void *pattern, char *line, size_t len) 470{ 471 regmatch_t pmatch[1]; 472 regmatch_t *pmp; 473 int eflags; 474 int rval; 475 regex_t *preg; 476 477 preg = pattern; 478 479 if (line == NULL) 480 return 0; 481 482 if (len == 0) { 483 pmp = NULL; 484 eflags = 0; 485 } 486 else { 487 pmatch[0].rm_so = 0; 488 pmatch[0].rm_eo = line[len - 1] == '\n' ? len - 1 : len; 489 pmp = pmatch; 490 eflags = REG_STARTEND; 491 } 492 493 switch ((rval = regexec(preg, line, 0, pmp, eflags))) { 494 case 0: 495 case REG_NOMATCH: 496 return rval == 0; 497 498 default: { 499 char errbuf[LINESIZE]; 500 (void)regerror(rval, preg, errbuf, sizeof(errbuf)); 501 (void)printf("regexec failed: '%s': %s\n", line, errbuf); 502 return -1; 503 }} 504} 505 506/* 507 * Look for (string) pattern in line. 508 * Return 1 if match found. 509 */ 510static int 511substrcmp(void *pattern, char *line, size_t len) 512{ 513 char *substr; 514 substr = pattern; 515 516 if (line == NULL) 517 return 0; 518 519 if (len) { 520 if (line[len - 1] == '\n') { 521 line[len - 1] = '\0'; 522 } 523 else { 524 char *cp; 525 cp = salloc(len + 1); 526 (void)strlcpy(cp, line, len + 1); 527 line = cp; 528 } 529 } 530 return strcasestr(line, substr) != NULL; 531} 532 533static regex_t preg; 534/* 535 * Determine the compare function and its argument based on the 536 * "regex-search" variable. 537 */ 538static int (* 539get_cmpfn(void **pattern, char *str) 540)(void *, char *, size_t) 541{ 542 char *val; 543 int cflags; 544 int e; 545 546 if ((val = value(ENAME_REGEX_SEARCH)) != NULL) { 547 cflags = REG_NOSUB; 548 val = skip_blank(val); 549 if (*val) { 550 if (is_substr(val, "icase")) 551 cflags |= REG_ICASE; 552 if (is_substr(val, "extended")) 553 cflags |= REG_EXTENDED; 554 /* 555 * Support "nospec", but don't document it as 556 * it may not be portable. 557 * NOTE: regcomp() will fail if "nospec" and 558 * "extended" are used together. 559 */ 560 if (is_substr(val, "nospec")) 561 cflags |= REG_NOSPEC; 562 } 563 if ((e = regcomp(&preg, str, cflags)) != 0) { 564 char errbuf[LINESIZE]; 565 (void)regerror(e, &preg, errbuf, sizeof(errbuf)); 566 (void)printf("regcomp failed: '%s': %s\n", str, errbuf); 567 return NULL; 568 } 569 *pattern = &preg; 570 return regexcmp; 571 } 572 573 *pattern = str; 574 return substrcmp; 575} 576 577/* 578 * Free any memory allocated by get_cmpfn() 579 */ 580static void 581free_cmparg(void *pattern) 582{ 583 if (pattern == &preg) 584 regfree(&preg); 585} 586 587/* 588 * Check the message body for the pattern. 589 */ 590static int 591matchbody(int (*cmpfn)(void *, char *, size_t), 592 void *pattern, struct message *mp, char const *fieldname __unused) 593{ 594 FILE *fp; 595 char *line; 596 size_t len; 597 int gotmatch; 598 599#ifdef __lint__ 600 fieldname = fieldname; 601#endif 602 /* 603 * Get a temporary file. 604 */ 605 { 606 char *tempname; 607 int fd; 608 609 (void)sasprintf(&tempname, "%s/mail.RbXXXXXXXXXX", tmpdir); 610 fp = NULL; 611 if ((fd = mkstemp(tempname)) != -1) { 612 (void)unlink(tempname); 613 if ((fp = Fdopen(fd, "w+")) == NULL) 614 (void)close(fd); 615 } 616 if (fp == NULL) { 617 warn("%s", tempname); 618 return -1; 619 } 620 } 621 622 /* 623 * Pump the (decoded) message body into the temp file. 624 */ 625 { 626#ifdef MIME_SUPPORT 627 struct mime_info *mip; 628 int retval; 629 630 mip = value(ENAME_MIME_DECODE_MSG) ? mime_decode_open(mp) 631 : NULL; 632 633 retval = mime_sendmessage(mp, fp, ignoreall, NULL, mip); 634 mime_decode_close(mip); 635 if (retval == -1) 636#else 637 if (sendmessage(mp, fp, ignoreall, NULL, NULL) == -1) 638#endif 639 { 640 warn("matchbody: mesg=%d", get_msgnum(mp)); 641 return -1; 642 } 643 } 644 /* 645 * XXX - should we read the entire body into a buffer so we 646 * can search across lines? 647 */ 648 rewind(fp); 649 gotmatch = 0; 650 while ((line = fgetln(fp, &len)) != NULL && len > 0) { 651 gotmatch = cmpfn(pattern, line, len); 652 if (gotmatch) 653 break; 654 } 655 (void)Fclose(fp); 656 657 return gotmatch; 658} 659 660/* 661 * Check the "To:", "Cc:", and "Bcc" fields for the pattern. 662 */ 663static int 664matchto(int (*cmpfn)(void *, char *, size_t), 665 void *pattern, struct message *mp, char const *fieldname __unused) 666{ 667 static const char *to_fields[] = { "to", "cc", "bcc", 0 }; 668 const char **to; 669 int gotmatch; 670 671#ifdef __lint__ 672 fieldname = fieldname; 673#endif 674 gotmatch = 0; 675 for (to = to_fields; *to; to++) { 676 char *field; 677 field = hfield(*to, mp); 678 gotmatch = cmpfn(pattern, field, 0); 679 if (gotmatch) 680 break; 681 } 682 return gotmatch; 683} 684 685/* 686 * Check a field for the pattern. 687 */ 688static int 689matchfield(int (*cmpfn)(void *, char *, size_t), 690 void *pattern, struct message *mp, char const *fieldname) 691{ 692 char *field; 693 694#ifdef __lint__ 695 fieldname = fieldname; 696#endif 697 field = hfield(fieldname, mp); 698 return cmpfn(pattern, field, 0); 699} 700 701/* 702 * Check the headline for the pattern. 703 */ 704static int 705matchfrom(int (*cmpfn)(void *, char *, size_t), 706 void *pattern, struct message *mp, char const *fieldname __unused) 707{ 708 char headline[LINESIZE]; 709 char *field; 710 711#ifdef __lint__ 712 fieldname = fieldname; 713#endif 714 (void)mail_readline(setinput(mp), headline, sizeof(headline)); 715 field = savestr(headline); 716 if (strncmp(field, "From ", 5) != 0) 717 return 1; 718 719 return cmpfn(pattern, field + 5, 0); 720} 721 722/* 723 * Check the sender for the pattern. 724 */ 725static int 726matchsender(int (*cmpfn)(void *, char *, size_t), 727 void *pattern, struct message *mp, char const *fieldname __unused) 728{ 729 char *field; 730 731#ifdef __lint__ 732 fieldname = fieldname; 733#endif 734 field = nameof(mp, 0); 735 return cmpfn(pattern, field, 0); 736} 737 738/* 739 * Interpret 'str' and check each message (1 thru 'msgCount') for a match. 740 * The 'str' has the format: [/[[x]:]y with the following meanings: 741 * 742 * y pattern 'y' is compared against the senders address. 743 * /y pattern 'y' is compared with the subject field. If 'y' is empty, 744 * the last search 'str' is used. 745 * /:y pattern 'y' is compared with the subject field. 746 * /x:y pattern 'y' is compared with the specified header field 'x' or 747 * the message body if 'x' == "body". 748 * 749 * The last two forms require "searchheaders" to be defined. 750 */ 751static int 752match_string(int *markarray, char *str, int msgCount) 753{ 754 int i; 755 int rval; 756 int (*matchfn)(int (*)(void *, char *, size_t), 757 void *, struct message *, char const *); 758 int (*cmpfn)(void *, char *, size_t); 759 void *cmparg; 760 char const *fieldname; 761 762 if (*str != '/') { 763 matchfn = matchsender; 764 fieldname = NULL; 765 } 766 else { 767 static char lastscan[STRINGLEN]; 768 char *cp; 769 770 str++; 771 if (*str == '\0') 772 str = lastscan; 773 else 774 (void)strlcpy(lastscan, str, sizeof(lastscan)); 775 776 if (value(ENAME_SEARCHHEADERS) == NULL || 777 (cp = strchr(str, ':')) == NULL) { 778 matchfn = matchfield; 779 fieldname = "subject"; 780 /* str = str; */ 781 } 782 else { 783 static const struct matchtbl_s { 784 char const *key; 785 size_t len; 786 char const *fieldname; 787 int (*matchfn)(int (*)(void *, char *, size_t), 788 void *, struct message *, char const *); 789 } matchtbl[] = { 790 #define X(a) a, sizeof(a) - 1 791 #define X_NULL NULL, 0 792 { X(":"), "subject", matchfield }, 793 { X("body:"), NULL, matchbody }, 794 { X("from:"), NULL, matchfrom }, 795 { X("to:"), NULL, matchto }, 796 { X_NULL, NULL, matchfield } 797 #undef X_NULL 798 #undef X 799 }; 800 const struct matchtbl_s *mtp; 801 size_t len; 802 /* 803 * Check for special cases! 804 * These checks are case sensitive so the true fields 805 * can be grabbed as mentioned in the manpage. 806 */ 807 cp++; 808 len = cp - str; 809 for (mtp = matchtbl; mtp->key; mtp++) { 810 if (len == mtp->len && 811 strncmp(str, mtp->key, len) == 0) 812 break; 813 } 814 matchfn = mtp->matchfn; 815 if (mtp->key) 816 fieldname = mtp->fieldname; 817 else { 818 char *p; 819 p = salloc(len); 820 (void)strlcpy(p, str, len); 821 fieldname = p; 822 } 823 str = cp; 824 } 825 } 826 827 if (*str == '\0') /* an empty string matches nothing instead of everything */ 828 return 0; 829 830 cmpfn = get_cmpfn(&cmparg, str); 831 if (cmpfn == NULL) 832 return -1; 833 834 rval = 0; 835 for (i = 1; i <= msgCount; i++) { 836 struct message *mp; 837 mp = get_message(i); 838 rval = matchfn(cmpfn, cmparg, mp, fieldname); 839 if (rval == -1) 840 break; 841 if (rval) 842 markarray[i - 1] = 1; 843 rval = 0; 844 } 845 846 free_cmparg(cmparg); /* free any memory allocated by get_cmpfn() */ 847 848 return rval; 849} 850 851 852/* 853 * Return the message number corresponding to the passed meta character. 854 */ 855static int 856metamess(int meta, int f) 857{ 858 int c, m; 859 struct message *mp; 860 861 c = meta; 862 switch (c) { 863 case '^': 864 /* 865 * First 'good' message left. 866 */ 867 for (mp = get_message(1); mp; mp = next_message(mp)) 868 if ((mp->m_flag & MDELETED) == f) 869 return get_msgnum(mp); 870 (void)printf("No applicable messages\n"); 871 return -1; 872 873 case '$': 874 /* 875 * Last 'good message left. 876 */ 877 for (mp = get_message(get_msgCount()); mp; mp = prev_message(mp)) 878 if ((mp->m_flag & MDELETED) == f) 879 return get_msgnum(mp); 880 (void)printf("No applicable messages\n"); 881 return -1; 882 883 case '.': 884 /* 885 * Current message. 886 */ 887 if (dot == NULL) { 888 (void)printf("No applicable messages\n"); 889 return -1; 890 } 891 m = get_msgnum(dot); 892 if ((dot->m_flag & MDELETED) != f) { 893 (void)printf("%d: Inappropriate message\n", m); 894 return -1; 895 } 896 return m; 897 898 default: 899 (void)printf("Unknown metachar (%c)\n", c); 900 return -1; 901 } 902} 903 904/* 905 * Check the passed message number for legality and proper flags. 906 * If f is MDELETED, then either kind will do. Otherwise, the message 907 * has to be undeleted. 908 */ 909static int 910check(int mesg, int f) 911{ 912 struct message *mp; 913 914 if ((mp = get_message(mesg)) == NULL) { 915 (void)printf("%d: Invalid message number\n", mesg); 916 return -1; 917 } 918 if (f != MDELETED && (mp->m_flag & MDELETED) != 0) { 919 (void)printf("%d: Inappropriate message\n", mesg); 920 return -1; 921 } 922 return 0; 923} 924 925 926static int 927markall_core(int *markarray, char **bufp, int f, int level) 928{ 929 enum token_e tok; 930 enum logic_op_e { 931 LOP_AND, 932 LOP_OR, 933 LOP_XOR 934 } logic_op; /* binary logic operation */ 935 int logic_invert; /* invert the result */ 936 int *tmparray; /* temporarly array with result */ 937 int msgCount; /* tmparray length and message count */ 938 int beg; /* first value of a range */ 939 int colmod; /* the colon-modifier for this group */ 940 int got_not; /* for syntax checking of '!' */ 941 int got_one; /* we have a message spec, valid or not */ 942 int got_bin; /* we have a pending binary operation */ 943 int i; 944 945 logic_op = LOP_OR; 946 logic_invert = 0; 947 colmod = 0; 948 949 msgCount = get_msgCount(); 950 tmparray = csalloc((size_t)msgCount, sizeof(*tmparray)); 951 952 beg = 0; 953 got_one = 0; 954 got_not = 0; 955 got_bin = 0; 956 957 while ((tok = scan(bufp)) != TEOL) { 958 if (tok == TERROR) 959 return -1; 960 961 /* 962 * Do some syntax checking. 963 */ 964 switch (tok) { 965 case TDASH: 966 case TPLUS: 967 case TDOLLAR: 968 case TUP: 969 case TDOT: 970 case TNUMBER: 971 break; 972 973 case TAND: 974 case TOR: 975 case TXOR: 976 if (!got_one) 977 return syntax_error("missing left operand"); 978 /*FALLTHROUGH*/ 979 default: 980 if (beg) 981 return syntax_error("end of range missing"); 982 break; 983 } 984 985 /* 986 * The main tok switch. 987 */ 988 switch (tok) { 989 struct message *mp; 990 991 case TERROR: /* trapped above */ 992 case TEOL: 993 assert(/*CONSTCOND*/0); 994 break; 995 996 case TUP: 997 if (got_one) { /* a possible logical xor */ 998 enum token_e t; 999 t = scan(bufp); /* peek ahead */ 1000 regret(t); 1001 lexstring[0] = '^'; /* restore lexstring */ 1002 lexstring[1] = '\0'; 1003 if (t != TDASH && t != TEOL && t != TCLOSE) { 1004 /* convert tok to TXOR and put 1005 * it back on the stack so we 1006 * can handle it consistently */ 1007 tok = TXOR; 1008 regret(tok); 1009 continue; 1010 } 1011 } 1012 /* FALLTHROUGH */ 1013 case TDOLLAR: 1014 case TDOT: 1015 lexnumber = metamess(lexstring[0], f); 1016 if (lexnumber == -1) 1017 return -1; 1018 /* FALLTHROUGH */ 1019 case TNUMBER: 1020 if (check(lexnumber, f)) 1021 return -1; 1022 number: 1023 got_one = 1; 1024 if (beg != 0) { 1025 if (lexnumber < beg) { 1026 (void)printf("invalid range: %d-%d\n", beg, lexnumber); 1027 return -1; 1028 } 1029 for (i = beg; i <= lexnumber; i++) 1030 tmparray[i - 1] = 1; 1031 1032 beg = 0; 1033 break; 1034 } 1035 beg = lexnumber; /* start of a range */ 1036 tok = scan(bufp); 1037 if (tok == TDASH) { 1038 continue; 1039 } 1040 else { 1041 regret(tok); 1042 tmparray[beg - 1] = 1; 1043 beg = 0; 1044 } 1045 break; 1046 1047 case TDASH: 1048 for (mp = prev_message(dot); mp; mp = prev_message(mp)) { 1049 if ((mp->m_flag & MDELETED) == 0) 1050 break; 1051 } 1052 if (mp == NULL) { 1053 (void)printf("Referencing before 1\n"); 1054 return -1; 1055 } 1056 lexnumber = get_msgnum(mp); 1057 goto number; 1058 1059 case TPLUS: 1060 for (mp = next_message(dot); mp; mp = next_message(mp)) { 1061 if ((mp->m_flag & MDELETED) == 0) 1062 break; 1063 } 1064 if (mp == NULL) { 1065 (void)printf("Referencing beyond EOF\n"); 1066 return -1; 1067 } 1068 lexnumber = get_msgnum(mp); 1069 goto number; 1070 1071 case TSTRING: 1072 if (lexstring[0] == ':') { /* colon modifier! */ 1073 colmod = get_colmod(colmod, lexstring + 1); 1074 if (colmod == -1) 1075 return -1; 1076 continue; 1077 } 1078 got_one = 1; 1079 if (match_string(tmparray, lexstring, msgCount) == -1) 1080 return -1; 1081 break; 1082 1083 case TSTAR: 1084 got_one = 1; 1085 for (i = 1; i <= msgCount; i++) 1086 tmparray[i - 1] = 1; 1087 break; 1088 1089 1090 /************** 1091 * Parentheses. 1092 */ 1093 case TOPEN: 1094 if (markall_core(tmparray, bufp, f, level + 1) == -1) 1095 return -1; 1096 break; 1097 1098 case TCLOSE: 1099 if (level == 0) 1100 return syntax_error("extra ')'"); 1101 goto done; 1102 1103 1104 /********************* 1105 * Logical operations. 1106 */ 1107 case TNOT: 1108 got_not = 1; 1109 logic_invert = ! logic_invert; 1110 continue; 1111 1112 /* 1113 * Binary operations. 1114 */ 1115 case TAND: 1116 if (got_not) 1117 return syntax_error("'!' precedes '&'"); 1118 got_bin = 1; 1119 logic_op = LOP_AND; 1120 continue; 1121 1122 case TOR: 1123 if (got_not) 1124 return syntax_error("'!' precedes '|'"); 1125 got_bin = 1; 1126 logic_op = LOP_OR; 1127 continue; 1128 1129 case TXOR: 1130 if (got_not) 1131 return syntax_error("'!' precedes logical '^'"); 1132 got_bin = 1; 1133 logic_op = LOP_XOR; 1134 continue; 1135 } 1136 1137 /* 1138 * Do the logic operations. 1139 */ 1140 if (logic_invert) 1141 for (i = 0; i < msgCount; i++) 1142 tmparray[i] = ! tmparray[i]; 1143 1144 switch (logic_op) { 1145 case LOP_AND: 1146 for (i = 0; i < msgCount; i++) 1147 markarray[i] &= tmparray[i]; 1148 break; 1149 1150 case LOP_OR: 1151 for (i = 0; i < msgCount; i++) 1152 markarray[i] |= tmparray[i]; 1153 break; 1154 1155 case LOP_XOR: 1156 for (i = 0; i < msgCount; i++) 1157 markarray[i] ^= tmparray[i]; 1158 break; 1159 } 1160 1161 /* 1162 * Clear the temporary array and reset the logic 1163 * operations. 1164 */ 1165 for (i = 0; i < msgCount; i++) 1166 tmparray[i] = 0; 1167 1168 logic_op = LOP_OR; 1169 logic_invert = 0; 1170 got_not = 0; 1171 got_bin = 0; 1172 } 1173 1174 if (beg) 1175 return syntax_error("end of range missing"); 1176 1177 if (level) 1178 return syntax_error("missing ')'"); 1179 1180 done: 1181 if (got_not) 1182 return syntax_error("trailing '!'"); 1183 1184 if (got_bin) 1185 return syntax_error("missing right operand"); 1186 1187 if (colmod != 0) { 1188 /* 1189 * If we have colon-modifiers but no messages 1190 * specifiec, then assume '*' was given. 1191 */ 1192 if (got_one == 0) 1193 for (i = 1; i <= msgCount; i++) 1194 markarray[i - 1] = 1; 1195 1196 for (i = 1; i <= msgCount; i++) { 1197 struct message *mp; 1198 if ((mp = get_message(i)) != NULL && 1199 ignore_message(mp->m_flag, colmod)) 1200 markarray[i - 1] = 0; 1201 } 1202 } 1203 return 0; 1204} 1205 1206static int 1207markall(char buf[], int f) 1208{ 1209 int i; 1210 int mc; 1211 int *markarray; 1212 int msgCount; 1213 struct message *mp; 1214 1215 msgCount = get_msgCount(); 1216 1217 /* 1218 * Clear all the previous message marks. 1219 */ 1220 for (i = 1; i <= msgCount; i++) 1221 if ((mp = get_message(i)) != NULL) 1222 mp->m_flag &= ~MMARK; 1223 1224 buf = skip_blank(buf); 1225 if (*buf == '\0') 1226 return 0; 1227 1228 scaninit(); 1229 markarray = csalloc((size_t)msgCount, sizeof(*markarray)); 1230 if (markall_core(markarray, &buf, f, 0) == -1) 1231 return -1; 1232 1233 /* 1234 * Transfer the markarray values to the messages. 1235 */ 1236 mc = 0; 1237 for (i = 1; i <= msgCount; i++) { 1238 if (markarray[i - 1] && 1239 (mp = get_message(i)) != NULL && 1240 (f == MDELETED || (mp->m_flag & MDELETED) == 0)) { 1241 mp->m_flag |= MMARK; 1242 mc++; 1243 } 1244 } 1245 1246 if (mc == 0) { 1247 (void)printf("No applicable messages.\n"); 1248 return -1; 1249 } 1250 return 0; 1251} 1252 1253/* 1254 * Convert the user string of message numbers and 1255 * store the numbers into vector. 1256 * 1257 * Returns the count of messages picked up or -1 on error. 1258 */ 1259PUBLIC int 1260getmsglist(char *buf, int *vector, int flags) 1261{ 1262 int *ip; 1263 struct message *mp; 1264 1265 if (get_msgCount() == 0) { 1266 *vector = 0; 1267 return 0; 1268 } 1269 if (markall(buf, flags) < 0) 1270 return -1; 1271 ip = vector; 1272 for (mp = get_message(1); mp; mp = next_message(mp)) 1273 if (mp->m_flag & MMARK) 1274 *ip++ = get_msgnum(mp); 1275 *ip = 0; 1276 return ip - vector; 1277} 1278 1279/* 1280 * Find the first message whose flags & m == f and return 1281 * its message number. 1282 */ 1283PUBLIC int 1284first(int f, int m) 1285{ 1286 struct message *mp; 1287 1288 if (get_msgCount() == 0) 1289 return 0; 1290 f &= MDELETED; 1291 m &= MDELETED; 1292 for (mp = dot; mp; mp = next_message(mp)) 1293 if ((mp->m_flag & m) == f) 1294 return get_msgnum(mp); 1295 for (mp = prev_message(dot); mp; mp = prev_message(mp)) 1296 if ((mp->m_flag & m) == f) 1297 return get_msgnum(mp); 1298 return 0; 1299} 1300 1301/* 1302 * Show all headers without paging. (-H flag) 1303 */ 1304__attribute__((__noreturn__)) 1305PUBLIC int 1306show_headers_and_exit(int flags) 1307{ 1308 struct message *mp; 1309 1310 flags &= CMMASK; 1311 for (mp = get_message(1); mp; mp = next_message(mp)) 1312 if (flags == 0 || !ignore_message(mp->m_flag, flags)) 1313 printhead(get_msgnum(mp)); 1314 1315 exit(0); 1316 /* NOTREACHED */ 1317} 1318 1319/* 1320 * A hack so -H can have an optional modifier as -H[:flags]. 1321 * 1322 * This depends a bit on the internals of getopt(). In particular, 1323 * for flags expecting an argument, argv[optind-1] must contain the 1324 * optarg and optarg must point to a substring of argv[optind-1] not a 1325 * copy of it. 1326 */ 1327PUBLIC int 1328get_Hflag(char **argv) 1329{ 1330 int flags; 1331 1332 flags = ~CMMASK; 1333 1334 if (optarg == NULL) /* We had an error, just get the flags. */ 1335 return flags; 1336 1337 if (*optarg != ':' || optarg == argv[optind - 1]) { 1338 optind--; 1339 optreset = 1; 1340 if (optarg != argv[optind]) { 1341 static char temparg[LINESIZE]; 1342 size_t optlen; 1343 size_t arglen; 1344 char *p; 1345 1346 optlen = strlen(optarg); 1347 arglen = strlen(argv[optind]); 1348 p = argv[optind] + arglen - optlen; 1349 optlen = MIN(optlen, sizeof(temparg) - 1); 1350 temparg[0] = '-'; 1351 (void)memmove(temparg + 1, p, optlen + 1); 1352 argv[optind] = temparg; 1353 } 1354 } 1355 else { 1356 flags = get_colmod(flags, optarg + 1); 1357 } 1358 return flags; 1359} 1360