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