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