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