1 /* 2 * Copyright (c) 1980, 1993 3 * The Regents of the University of California. All rights reserved. 4 * 5 * %sccs.include.redist.c% 6 */ 7 8 #ifndef lint 9 static char sccsid[] = "@(#)list.c 8.2 (Berkeley) 04/19/94"; 10 #endif /* not lint */ 11 12 #include "rcv.h" 13 #include <ctype.h> 14 #include "extern.h" 15 16 /* 17 * Mail -- a mail program 18 * 19 * Message list handling. 20 */ 21 22 /* 23 * Convert the user string of message numbers and 24 * store the numbers into vector. 25 * 26 * Returns the count of messages picked up or -1 on error. 27 */ 28 int 29 getmsglist(buf, vector, flags) 30 char *buf; 31 int *vector, flags; 32 { 33 register int *ip; 34 register struct message *mp; 35 36 if (msgCount == 0) { 37 *vector = 0; 38 return 0; 39 } 40 if (markall(buf, flags) < 0) 41 return(-1); 42 ip = vector; 43 for (mp = &message[0]; mp < &message[msgCount]; mp++) 44 if (mp->m_flag & MMARK) 45 *ip++ = mp - &message[0] + 1; 46 *ip = 0; 47 return(ip - vector); 48 } 49 50 /* 51 * Mark all messages that the user wanted from the command 52 * line in the message structure. Return 0 on success, -1 53 * on error. 54 */ 55 56 /* 57 * Bit values for colon modifiers. 58 */ 59 60 #define CMNEW 01 /* New messages */ 61 #define CMOLD 02 /* Old messages */ 62 #define CMUNREAD 04 /* Unread messages */ 63 #define CMDELETED 010 /* Deleted messages */ 64 #define CMREAD 020 /* Read messages */ 65 66 /* 67 * The following table describes the letters which can follow 68 * the colon and gives the corresponding modifier bit. 69 */ 70 71 struct coltab { 72 char co_char; /* What to find past : */ 73 int co_bit; /* Associated modifier bit */ 74 int co_mask; /* m_status bits to mask */ 75 int co_equal; /* ... must equal this */ 76 } coltab[] = { 77 'n', CMNEW, MNEW, MNEW, 78 'o', CMOLD, MNEW, 0, 79 'u', CMUNREAD, MREAD, 0, 80 'd', CMDELETED, MDELETED, MDELETED, 81 'r', CMREAD, MREAD, MREAD, 82 0, 0, 0, 0 83 }; 84 85 static int lastcolmod; 86 87 int 88 markall(buf, f) 89 char buf[]; 90 int f; 91 { 92 register char **np; 93 register int i; 94 register struct message *mp; 95 char *namelist[NMLSIZE], *bufp; 96 int tok, beg, mc, star, other, valdot, colmod, colresult; 97 98 valdot = dot - &message[0] + 1; 99 colmod = 0; 100 for (i = 1; i <= msgCount; i++) 101 unmark(i); 102 bufp = buf; 103 mc = 0; 104 np = &namelist[0]; 105 scaninit(); 106 tok = scan(&bufp); 107 star = 0; 108 other = 0; 109 beg = 0; 110 while (tok != TEOL) { 111 switch (tok) { 112 case TNUMBER: 113 number: 114 if (star) { 115 printf("No numbers mixed with *\n"); 116 return(-1); 117 } 118 mc++; 119 other++; 120 if (beg != 0) { 121 if (check(lexnumber, f)) 122 return(-1); 123 for (i = beg; i <= lexnumber; i++) 124 if (f == MDELETED || (message[i - 1].m_flag & MDELETED) == 0) 125 mark(i); 126 beg = 0; 127 break; 128 } 129 beg = lexnumber; 130 if (check(beg, f)) 131 return(-1); 132 tok = scan(&bufp); 133 regret(tok); 134 if (tok != TDASH) { 135 mark(beg); 136 beg = 0; 137 } 138 break; 139 140 case TPLUS: 141 if (beg != 0) { 142 printf("Non-numeric second argument\n"); 143 return(-1); 144 } 145 i = valdot; 146 do { 147 i++; 148 if (i > msgCount) { 149 printf("Referencing beyond EOF\n"); 150 return(-1); 151 } 152 } while ((message[i - 1].m_flag & MDELETED) != f); 153 mark(i); 154 break; 155 156 case TDASH: 157 if (beg == 0) { 158 i = valdot; 159 do { 160 i--; 161 if (i <= 0) { 162 printf("Referencing before 1\n"); 163 return(-1); 164 } 165 } while ((message[i - 1].m_flag & MDELETED) != f); 166 mark(i); 167 } 168 break; 169 170 case TSTRING: 171 if (beg != 0) { 172 printf("Non-numeric second argument\n"); 173 return(-1); 174 } 175 other++; 176 if (lexstring[0] == ':') { 177 colresult = evalcol(lexstring[1]); 178 if (colresult == 0) { 179 printf("Unknown colon modifier \"%s\"\n", 180 lexstring); 181 return(-1); 182 } 183 colmod |= colresult; 184 } 185 else 186 *np++ = savestr(lexstring); 187 break; 188 189 case TDOLLAR: 190 case TUP: 191 case TDOT: 192 lexnumber = metamess(lexstring[0], f); 193 if (lexnumber == -1) 194 return(-1); 195 goto number; 196 197 case TSTAR: 198 if (other) { 199 printf("Can't mix \"*\" with anything\n"); 200 return(-1); 201 } 202 star++; 203 break; 204 205 case TERROR: 206 return -1; 207 } 208 tok = scan(&bufp); 209 } 210 lastcolmod = colmod; 211 *np = NOSTR; 212 mc = 0; 213 if (star) { 214 for (i = 0; i < msgCount; i++) 215 if ((message[i].m_flag & MDELETED) == f) { 216 mark(i+1); 217 mc++; 218 } 219 if (mc == 0) { 220 printf("No applicable messages.\n"); 221 return(-1); 222 } 223 return(0); 224 } 225 226 /* 227 * If no numbers were given, mark all of the messages, 228 * so that we can unmark any whose sender was not selected 229 * if any user names were given. 230 */ 231 232 if ((np > namelist || colmod != 0) && mc == 0) 233 for (i = 1; i <= msgCount; i++) 234 if ((message[i-1].m_flag & MDELETED) == f) 235 mark(i); 236 237 /* 238 * If any names were given, go through and eliminate any 239 * messages whose senders were not requested. 240 */ 241 242 if (np > namelist) { 243 for (i = 1; i <= msgCount; i++) { 244 for (mc = 0, np = &namelist[0]; *np != NOSTR; np++) 245 if (**np == '/') { 246 if (matchsubj(*np, i)) { 247 mc++; 248 break; 249 } 250 } 251 else { 252 if (matchsender(*np, i)) { 253 mc++; 254 break; 255 } 256 } 257 if (mc == 0) 258 unmark(i); 259 } 260 261 /* 262 * Make sure we got some decent messages. 263 */ 264 265 mc = 0; 266 for (i = 1; i <= msgCount; i++) 267 if (message[i-1].m_flag & MMARK) { 268 mc++; 269 break; 270 } 271 if (mc == 0) { 272 printf("No applicable messages from {%s", 273 namelist[0]); 274 for (np = &namelist[1]; *np != NOSTR; np++) 275 printf(", %s", *np); 276 printf("}\n"); 277 return(-1); 278 } 279 } 280 281 /* 282 * If any colon modifiers were given, go through and 283 * unmark any messages which do not satisfy the modifiers. 284 */ 285 286 if (colmod != 0) { 287 for (i = 1; i <= msgCount; i++) { 288 register struct coltab *colp; 289 290 mp = &message[i - 1]; 291 for (colp = &coltab[0]; colp->co_char; colp++) 292 if (colp->co_bit & colmod) 293 if ((mp->m_flag & colp->co_mask) 294 != colp->co_equal) 295 unmark(i); 296 297 } 298 for (mp = &message[0]; mp < &message[msgCount]; mp++) 299 if (mp->m_flag & MMARK) 300 break; 301 if (mp >= &message[msgCount]) { 302 register struct coltab *colp; 303 304 printf("No messages satisfy"); 305 for (colp = &coltab[0]; colp->co_char; colp++) 306 if (colp->co_bit & colmod) 307 printf(" :%c", colp->co_char); 308 printf("\n"); 309 return(-1); 310 } 311 } 312 return(0); 313 } 314 315 /* 316 * Turn the character after a colon modifier into a bit 317 * value. 318 */ 319 int 320 evalcol(col) 321 int col; 322 { 323 register struct coltab *colp; 324 325 if (col == 0) 326 return(lastcolmod); 327 for (colp = &coltab[0]; colp->co_char; colp++) 328 if (colp->co_char == col) 329 return(colp->co_bit); 330 return(0); 331 } 332 333 /* 334 * Check the passed message number for legality and proper flags. 335 * If f is MDELETED, then either kind will do. Otherwise, the message 336 * has to be undeleted. 337 */ 338 int 339 check(mesg, f) 340 int mesg, f; 341 { 342 register struct message *mp; 343 344 if (mesg < 1 || mesg > msgCount) { 345 printf("%d: Invalid message number\n", mesg); 346 return(-1); 347 } 348 mp = &message[mesg-1]; 349 if (f != MDELETED && (mp->m_flag & MDELETED) != 0) { 350 printf("%d: Inappropriate message\n", mesg); 351 return(-1); 352 } 353 return(0); 354 } 355 356 /* 357 * Scan out the list of string arguments, shell style 358 * for a RAWLIST. 359 */ 360 int 361 getrawlist(line, argv, argc) 362 char line[]; 363 char **argv; 364 int argc; 365 { 366 register char c, *cp, *cp2, quotec; 367 int argn; 368 char linebuf[BUFSIZ]; 369 370 argn = 0; 371 cp = line; 372 for (;;) { 373 for (; *cp == ' ' || *cp == '\t'; cp++) 374 ; 375 if (*cp == '\0') 376 break; 377 if (argn >= argc - 1) { 378 printf( 379 "Too many elements in the list; excess discarded.\n"); 380 break; 381 } 382 cp2 = linebuf; 383 quotec = '\0'; 384 while ((c = *cp) != '\0') { 385 cp++; 386 if (quotec != '\0') { 387 if (c == quotec) 388 quotec = '\0'; 389 else if (c == '\\') 390 switch (c = *cp++) { 391 case '\0': 392 *cp2++ = '\\'; 393 cp--; 394 break; 395 case '0': case '1': case '2': case '3': 396 case '4': case '5': case '6': case '7': 397 c -= '0'; 398 if (*cp >= '0' && *cp <= '7') 399 c = c * 8 + *cp++ - '0'; 400 if (*cp >= '0' && *cp <= '7') 401 c = c * 8 + *cp++ - '0'; 402 *cp2++ = c; 403 break; 404 case 'b': 405 *cp2++ = '\b'; 406 break; 407 case 'f': 408 *cp2++ = '\f'; 409 break; 410 case 'n': 411 *cp2++ = '\n'; 412 break; 413 case 'r': 414 *cp2++ = '\r'; 415 break; 416 case 't': 417 *cp2++ = '\t'; 418 break; 419 case 'v': 420 *cp2++ = '\v'; 421 break; 422 default: 423 *cp2++ = c; 424 } 425 else if (c == '^') { 426 c = *cp++; 427 if (c == '?') 428 *cp2++ = '\177'; 429 /* null doesn't show up anyway */ 430 else if (c >= 'A' && c <= '_' || 431 c >= 'a' && c <= 'z') 432 *cp2++ = c & 037; 433 else { 434 *cp2++ = '^'; 435 cp--; 436 } 437 } else 438 *cp2++ = c; 439 } else if (c == '"' || c == '\'') 440 quotec = c; 441 else if (c == ' ' || c == '\t') 442 break; 443 else 444 *cp2++ = c; 445 } 446 *cp2 = '\0'; 447 argv[argn++] = savestr(linebuf); 448 } 449 argv[argn] = NOSTR; 450 return argn; 451 } 452 453 /* 454 * scan out a single lexical item and return its token number, 455 * updating the string pointer passed **p. Also, store the value 456 * of the number or string scanned in lexnumber or lexstring as 457 * appropriate. In any event, store the scanned `thing' in lexstring. 458 */ 459 460 struct lex { 461 char l_char; 462 char l_token; 463 } singles[] = { 464 '$', TDOLLAR, 465 '.', TDOT, 466 '^', TUP, 467 '*', TSTAR, 468 '-', TDASH, 469 '+', TPLUS, 470 '(', TOPEN, 471 ')', TCLOSE, 472 0, 0 473 }; 474 475 int 476 scan(sp) 477 char **sp; 478 { 479 register char *cp, *cp2; 480 register int c; 481 register struct lex *lp; 482 int quotec; 483 484 if (regretp >= 0) { 485 strcpy(lexstring, string_stack[regretp]); 486 lexnumber = numberstack[regretp]; 487 return(regretstack[regretp--]); 488 } 489 cp = *sp; 490 cp2 = lexstring; 491 c = *cp++; 492 493 /* 494 * strip away leading white space. 495 */ 496 497 while (c == ' ' || c == '\t') 498 c = *cp++; 499 500 /* 501 * If no characters remain, we are at end of line, 502 * so report that. 503 */ 504 505 if (c == '\0') { 506 *sp = --cp; 507 return(TEOL); 508 } 509 510 /* 511 * If the leading character is a digit, scan 512 * the number and convert it on the fly. 513 * Return TNUMBER when done. 514 */ 515 516 if (isdigit(c)) { 517 lexnumber = 0; 518 while (isdigit(c)) { 519 lexnumber = lexnumber*10 + c - '0'; 520 *cp2++ = c; 521 c = *cp++; 522 } 523 *cp2 = '\0'; 524 *sp = --cp; 525 return(TNUMBER); 526 } 527 528 /* 529 * Check for single character tokens; return such 530 * if found. 531 */ 532 533 for (lp = &singles[0]; lp->l_char != 0; lp++) 534 if (c == lp->l_char) { 535 lexstring[0] = c; 536 lexstring[1] = '\0'; 537 *sp = cp; 538 return(lp->l_token); 539 } 540 541 /* 542 * We've got a string! Copy all the characters 543 * of the string into lexstring, until we see 544 * a null, space, or tab. 545 * If the lead character is a " or ', save it 546 * and scan until you get another. 547 */ 548 549 quotec = 0; 550 if (c == '\'' || c == '"') { 551 quotec = c; 552 c = *cp++; 553 } 554 while (c != '\0') { 555 if (c == quotec) { 556 cp++; 557 break; 558 } 559 if (quotec == 0 && (c == ' ' || c == '\t')) 560 break; 561 if (cp2 - lexstring < STRINGLEN-1) 562 *cp2++ = c; 563 c = *cp++; 564 } 565 if (quotec && c == 0) { 566 fprintf(stderr, "Missing %c\n", quotec); 567 return TERROR; 568 } 569 *sp = --cp; 570 *cp2 = '\0'; 571 return(TSTRING); 572 } 573 574 /* 575 * Unscan the named token by pushing it onto the regret stack. 576 */ 577 void 578 regret(token) 579 int token; 580 { 581 if (++regretp >= REGDEP) 582 panic("Too many regrets"); 583 regretstack[regretp] = token; 584 lexstring[STRINGLEN-1] = '\0'; 585 string_stack[regretp] = savestr(lexstring); 586 numberstack[regretp] = lexnumber; 587 } 588 589 /* 590 * Reset all the scanner global variables. 591 */ 592 void 593 scaninit() 594 { 595 regretp = -1; 596 } 597 598 /* 599 * Find the first message whose flags & m == f and return 600 * its message number. 601 */ 602 int 603 first(f, m) 604 int f, m; 605 { 606 register struct message *mp; 607 608 if (msgCount == 0) 609 return 0; 610 f &= MDELETED; 611 m &= MDELETED; 612 for (mp = dot; mp < &message[msgCount]; mp++) 613 if ((mp->m_flag & m) == f) 614 return mp - message + 1; 615 for (mp = dot-1; mp >= &message[0]; mp--) 616 if ((mp->m_flag & m) == f) 617 return mp - message + 1; 618 return 0; 619 } 620 621 /* 622 * See if the passed name sent the passed message number. Return true 623 * if so. 624 */ 625 int 626 matchsender(str, mesg) 627 char *str; 628 int mesg; 629 { 630 register char *cp, *cp2, *backup; 631 632 if (!*str) /* null string matches nothing instead of everything */ 633 return 0; 634 backup = cp2 = nameof(&message[mesg - 1], 0); 635 cp = str; 636 while (*cp2) { 637 if (*cp == 0) 638 return(1); 639 if (raise(*cp++) != raise(*cp2++)) { 640 cp2 = ++backup; 641 cp = str; 642 } 643 } 644 return(*cp == 0); 645 } 646 647 /* 648 * See if the given string matches inside the subject field of the 649 * given message. For the purpose of the scan, we ignore case differences. 650 * If it does, return true. The string search argument is assumed to 651 * have the form "/search-string." If it is of the form "/," we use the 652 * previous search string. 653 */ 654 655 char lastscan[128]; 656 int 657 matchsubj(str, mesg) 658 char *str; 659 int mesg; 660 { 661 register struct message *mp; 662 register char *cp, *cp2, *backup; 663 664 str++; 665 if (strlen(str) == 0) 666 str = lastscan; 667 else 668 strcpy(lastscan, str); 669 mp = &message[mesg-1]; 670 671 /* 672 * Now look, ignoring case, for the word in the string. 673 */ 674 675 if (value("searchheaders") && (cp = index(str, ':'))) { 676 *cp++ = '\0'; 677 cp2 = hfield(str, mp); 678 cp[-1] = ':'; 679 str = cp; 680 } else { 681 cp = str; 682 cp2 = hfield("subject", mp); 683 } 684 if (cp2 == NOSTR) 685 return(0); 686 backup = cp2; 687 while (*cp2) { 688 if (*cp == 0) 689 return(1); 690 if (raise(*cp++) != raise(*cp2++)) { 691 cp2 = ++backup; 692 cp = str; 693 } 694 } 695 return(*cp == 0); 696 } 697 698 /* 699 * Mark the named message by setting its mark bit. 700 */ 701 void 702 mark(mesg) 703 int mesg; 704 { 705 register int i; 706 707 i = mesg; 708 if (i < 1 || i > msgCount) 709 panic("Bad message number to mark"); 710 message[i-1].m_flag |= MMARK; 711 } 712 713 /* 714 * Unmark the named message. 715 */ 716 void 717 unmark(mesg) 718 int mesg; 719 { 720 register int i; 721 722 i = mesg; 723 if (i < 1 || i > msgCount) 724 panic("Bad message number to unmark"); 725 message[i-1].m_flag &= ~MMARK; 726 } 727 728 /* 729 * Return the message number corresponding to the passed meta character. 730 */ 731 int 732 metamess(meta, f) 733 int meta, f; 734 { 735 register int c, m; 736 register struct message *mp; 737 738 c = meta; 739 switch (c) { 740 case '^': 741 /* 742 * First 'good' message left. 743 */ 744 for (mp = &message[0]; mp < &message[msgCount]; mp++) 745 if ((mp->m_flag & MDELETED) == f) 746 return(mp - &message[0] + 1); 747 printf("No applicable messages\n"); 748 return(-1); 749 750 case '$': 751 /* 752 * Last 'good message left. 753 */ 754 for (mp = &message[msgCount-1]; mp >= &message[0]; mp--) 755 if ((mp->m_flag & MDELETED) == f) 756 return(mp - &message[0] + 1); 757 printf("No applicable messages\n"); 758 return(-1); 759 760 case '.': 761 /* 762 * Current message. 763 */ 764 m = dot - &message[0] + 1; 765 if ((dot->m_flag & MDELETED) != f) { 766 printf("%d: Inappropriate message\n", m); 767 return(-1); 768 } 769 return(m); 770 771 default: 772 printf("Unknown metachar (%c)\n", c); 773 return(-1); 774 } 775 } 776