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