4.4BSD snapshot (revision 8.1); add 1993 to copyright
[unix-history] / usr / src / usr.bin / mail / list.c
CommitLineData
9552e6b8 1/*
a12ff486
KB
2 * Copyright (c) 1980, 1993
3 * The Regents of the University of California. All rights reserved.
0c5f72fb 4 *
f15db449 5 * %sccs.include.redist.c%
9552e6b8
DF
6 */
7
acfc7e9b 8#ifndef lint
a12ff486 9static char sccsid[] = "@(#)list.c 8.1 (Berkeley) %G%";
acfc7e9b 10#endif /* not lint */
b3149556
KS
11
12#include "rcv.h"
13#include <ctype.h>
a0d23834 14#include "extern.h"
b3149556
KS
15
16/*
17 * Mail -- a mail program
18 *
19 * Message list handling.
20 */
21
b3149556
KS
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 */
a0d23834 28int
b3149556
KS
29getmsglist(buf, vector, flags)
30 char *buf;
a0d23834 31 int *vector, flags;
b3149556
KS
32{
33 register int *ip;
34 register struct message *mp;
35
c1241556
EW
36 if (msgCount == 0) {
37 *vector = 0;
38 return 0;
39 }
b3149556
KS
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;
c1241556 46 *ip = 0;
b3149556
KS
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
2919b83d
KS
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
71struct 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
85static int lastcolmod;
86
a0d23834 87int
b3149556
KS
88markall(buf, f)
89 char buf[];
a0d23834 90 int f;
b3149556
KS
91{
92 register char **np;
93 register int i;
2919b83d 94 register struct message *mp;
b3149556 95 char *namelist[NMLSIZE], *bufp;
2919b83d 96 int tok, beg, mc, star, other, valdot, colmod, colresult;
b3149556
KS
97
98 valdot = dot - &message[0] + 1;
2919b83d 99 colmod = 0;
b3149556
KS
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:
113number:
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++)
20878753 124 if (f == MDELETED || (message[i - 1].m_flag & MDELETED) == 0)
c9c93837 125 mark(i);
b3149556
KS
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 }
9745f5e0
S
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);
b3149556
KS
154 break;
155
156 case TDASH:
157 if (beg == 0) {
9745f5e0
S
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);
b3149556
KS
167 }
168 break;
169
170 case TSTRING:
171 if (beg != 0) {
172 printf("Non-numeric second argument\n");
173 return(-1);
174 }
175 other++;
2919b83d
KS
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);
b3149556
KS
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;
a8a981d5
EW
204
205 case TERROR:
206 return -1;
b3149556
KS
207 }
208 tok = scan(&bufp);
209 }
2919b83d 210 lastcolmod = colmod;
b3149556
KS
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
2919b83d 232 if ((np > namelist || colmod != 0) && mc == 0)
b3149556 233 for (i = 1; i <= msgCount; i++)
9745f5e0 234 if ((message[i-1].m_flag & MDELETED) == f)
b3149556
KS
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++)
f7a4f91c
KS
245 if (**np == '/') {
246 if (matchsubj(*np, i)) {
247 mc++;
248 break;
249 }
250 }
251 else {
a8a981d5 252 if (matchsender(*np, i)) {
f7a4f91c
KS
253 mc++;
254 break;
255 }
b3149556
KS
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 }
2919b83d
KS
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 */
a0d23834 319int
2919b83d 320evalcol(col)
a0d23834 321 int col;
2919b83d
KS
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);
b3149556
KS
330 return(0);
331}
332
333/*
334 * Check the passed message number for legality and proper flags.
20878753
EW
335 * If f is MDELETED, then either kind will do. Otherwise, the message
336 * has to be undeleted.
b3149556 337 */
a0d23834 338int
b3149556 339check(mesg, f)
a0d23834 340 int mesg, f;
b3149556
KS
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];
20878753 349 if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
b3149556
KS
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 */
a0d23834 360int
fee4667a 361getrawlist(line, argv, argc)
b3149556
KS
362 char line[];
363 char **argv;
fee4667a 364 int argc;
b3149556 365{
433e5791
EW
366 register char c, *cp, *cp2, quotec;
367 int argn;
368 char linebuf[BUFSIZ];
b3149556 369
433e5791 370 argn = 0;
b3149556 371 cp = line;
433e5791
EW
372 for (;;) {
373 for (; *cp == ' ' || *cp == '\t'; cp++)
374 ;
375 if (*cp == '\0')
b3149556 376 break;
433e5791
EW
377 if (argn >= argc - 1) {
378 printf(
379 "Too many elements in the list; excess discarded.\n");
fee4667a
S
380 break;
381 }
433e5791
EW
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++ = *--cp;
393 break;
394 case '0': case '1': case '2': case '3':
395 case '4': case '5': case '6': case '7':
396 c -= '0';
397 if (*cp >= '0' && *cp <= '7')
398 c = c * 8 + *cp++ - '0';
399 if (*cp >= '0' && *cp <= '7')
400 c = c * 8 + *cp++ - '0';
401 *cp2++ = c;
402 break;
403 case 'b':
404 *cp2++ = '\b';
405 break;
406 case 'f':
407 *cp2++ = '\f';
408 break;
409 case 'n':
410 *cp2++ = '\n';
411 break;
412 case 'r':
413 *cp2++ = '\r';
414 break;
415 case 't':
416 *cp2++ = '\t';
417 break;
418 case 'v':
419 *cp2++ = '\v';
420 break;
421 }
422 else if (c == '^') {
423 c = *cp++;
424 if (c == '?')
425 *cp2++ = '\177';
426 /* null doesn't show up anyway */
427 else if (c >= 'A' && c <= '_' ||
428 c >= 'a' && c <= 'z')
429 *cp2++ &= 037;
430 else
431 *cp2++ = *--cp;
432 } else
433 *cp2++ = c;
434 } else if (c == '"' || c == '\'')
435 quotec = c;
436 else if (c == ' ' || c == '\t')
437 break;
438 else
439 *cp2++ = c;
440 }
441 *cp2 = '\0';
442 argv[argn++] = savestr(linebuf);
b3149556 443 }
433e5791
EW
444 argv[argn] = NOSTR;
445 return argn;
b3149556
KS
446}
447
448/*
449 * scan out a single lexical item and return its token number,
450 * updating the string pointer passed **p. Also, store the value
451 * of the number or string scanned in lexnumber or lexstring as
452 * appropriate. In any event, store the scanned `thing' in lexstring.
453 */
454
455struct lex {
456 char l_char;
457 char l_token;
458} singles[] = {
459 '$', TDOLLAR,
460 '.', TDOT,
461 '^', TUP,
462 '*', TSTAR,
463 '-', TDASH,
464 '+', TPLUS,
465 '(', TOPEN,
466 ')', TCLOSE,
467 0, 0
468};
469
a0d23834 470int
b3149556
KS
471scan(sp)
472 char **sp;
473{
474 register char *cp, *cp2;
475 register int c;
476 register struct lex *lp;
477 int quotec;
478
479 if (regretp >= 0) {
85d5837f 480 strcpy(lexstring, string_stack[regretp]);
b3149556
KS
481 lexnumber = numberstack[regretp];
482 return(regretstack[regretp--]);
483 }
484 cp = *sp;
485 cp2 = lexstring;
486 c = *cp++;
487
488 /*
489 * strip away leading white space.
490 */
491
470c33f3 492 while (c == ' ' || c == '\t')
b3149556
KS
493 c = *cp++;
494
495 /*
496 * If no characters remain, we are at end of line,
497 * so report that.
498 */
499
500 if (c == '\0') {
501 *sp = --cp;
502 return(TEOL);
503 }
504
505 /*
506 * If the leading character is a digit, scan
507 * the number and convert it on the fly.
508 * Return TNUMBER when done.
509 */
510
511 if (isdigit(c)) {
512 lexnumber = 0;
513 while (isdigit(c)) {
514 lexnumber = lexnumber*10 + c - '0';
515 *cp2++ = c;
516 c = *cp++;
517 }
518 *cp2 = '\0';
519 *sp = --cp;
520 return(TNUMBER);
521 }
522
523 /*
524 * Check for single character tokens; return such
525 * if found.
526 */
527
528 for (lp = &singles[0]; lp->l_char != 0; lp++)
529 if (c == lp->l_char) {
530 lexstring[0] = c;
531 lexstring[1] = '\0';
532 *sp = cp;
533 return(lp->l_token);
534 }
535
536 /*
537 * We've got a string! Copy all the characters
538 * of the string into lexstring, until we see
539 * a null, space, or tab.
540 * If the lead character is a " or ', save it
541 * and scan until you get another.
542 */
543
544 quotec = 0;
470c33f3 545 if (c == '\'' || c == '"') {
b3149556
KS
546 quotec = c;
547 c = *cp++;
548 }
549 while (c != '\0') {
a8a981d5
EW
550 if (c == quotec) {
551 cp++;
b3149556 552 break;
a8a981d5 553 }
470c33f3 554 if (quotec == 0 && (c == ' ' || c == '\t'))
b3149556
KS
555 break;
556 if (cp2 - lexstring < STRINGLEN-1)
557 *cp2++ = c;
558 c = *cp++;
559 }
a8a981d5 560 if (quotec && c == 0) {
b3149556 561 fprintf(stderr, "Missing %c\n", quotec);
a8a981d5
EW
562 return TERROR;
563 }
b3149556
KS
564 *sp = --cp;
565 *cp2 = '\0';
566 return(TSTRING);
567}
568
569/*
570 * Unscan the named token by pushing it onto the regret stack.
571 */
a0d23834 572void
b3149556 573regret(token)
a0d23834 574 int token;
b3149556
KS
575{
576 if (++regretp >= REGDEP)
577 panic("Too many regrets");
578 regretstack[regretp] = token;
579 lexstring[STRINGLEN-1] = '\0';
85d5837f 580 string_stack[regretp] = savestr(lexstring);
b3149556
KS
581 numberstack[regretp] = lexnumber;
582}
583
584/*
585 * Reset all the scanner global variables.
586 */
a0d23834 587void
b3149556
KS
588scaninit()
589{
590 regretp = -1;
591}
592
593/*
594 * Find the first message whose flags & m == f and return
595 * its message number.
596 */
a0d23834 597int
b3149556 598first(f, m)
a0d23834 599 int f, m;
b3149556 600{
b3149556
KS
601 register struct message *mp;
602
c1241556
EW
603 if (msgCount == 0)
604 return 0;
b3149556
KS
605 f &= MDELETED;
606 m &= MDELETED;
c1241556 607 for (mp = dot; mp < &message[msgCount]; mp++)
b3149556 608 if ((mp->m_flag & m) == f)
c1241556
EW
609 return mp - message + 1;
610 for (mp = dot-1; mp >= &message[0]; mp--)
b3149556 611 if ((mp->m_flag & m) == f)
c1241556
EW
612 return mp - message + 1;
613 return 0;
b3149556
KS
614}
615
616/*
617 * See if the passed name sent the passed message number. Return true
618 * if so.
619 */
a0d23834 620int
a8a981d5 621matchsender(str, mesg)
b3149556 622 char *str;
a0d23834 623 int mesg;
b3149556 624{
9745f5e0 625 register char *cp, *cp2, *backup;
b3149556 626
a8a981d5
EW
627 if (!*str) /* null string matches nothing instead of everything */
628 return 0;
629 backup = cp2 = nameof(&message[mesg - 1], 0);
9745f5e0
S
630 cp = str;
631 while (*cp2) {
632 if (*cp == 0)
633 return(1);
634 if (raise(*cp++) != raise(*cp2++)) {
635 cp2 = ++backup;
636 cp = str;
637 }
638 }
639 return(*cp == 0);
b3149556
KS
640}
641
f7a4f91c
KS
642/*
643 * See if the given string matches inside the subject field of the
644 * given message. For the purpose of the scan, we ignore case differences.
645 * If it does, return true. The string search argument is assumed to
646 * have the form "/search-string." If it is of the form "/," we use the
647 * previous search string.
648 */
649
650char lastscan[128];
a0d23834 651int
f7a4f91c
KS
652matchsubj(str, mesg)
653 char *str;
a0d23834 654 int mesg;
f7a4f91c
KS
655{
656 register struct message *mp;
312bdff6 657 register char *cp, *cp2, *backup;
f7a4f91c
KS
658
659 str++;
660 if (strlen(str) == 0)
661 str = lastscan;
662 else
663 strcpy(lastscan, str);
664 mp = &message[mesg-1];
665
666 /*
667 * Now look, ignoring case, for the word in the string.
668 */
669
c2e1b7e6
EW
670 if (value("searchheaders") && (cp = index(str, ':'))) {
671 *cp++ = '\0';
672 cp2 = hfield(str, mp);
673 cp[-1] = ':';
674 str = cp;
675 } else {
676 cp = str;
677 cp2 = hfield("subject", mp);
678 }
f7a4f91c
KS
679 if (cp2 == NOSTR)
680 return(0);
312bdff6 681 backup = cp2;
f7a4f91c
KS
682 while (*cp2) {
683 if (*cp == 0)
684 return(1);
312bdff6
KS
685 if (raise(*cp++) != raise(*cp2++)) {
686 cp2 = ++backup;
f7a4f91c 687 cp = str;
312bdff6 688 }
f7a4f91c
KS
689 }
690 return(*cp == 0);
691}
692
b3149556
KS
693/*
694 * Mark the named message by setting its mark bit.
695 */
a0d23834 696void
b3149556 697mark(mesg)
a0d23834 698 int mesg;
b3149556
KS
699{
700 register int i;
701
702 i = mesg;
703 if (i < 1 || i > msgCount)
704 panic("Bad message number to mark");
705 message[i-1].m_flag |= MMARK;
706}
707
708/*
709 * Unmark the named message.
710 */
a0d23834 711void
b3149556 712unmark(mesg)
a0d23834 713 int mesg;
b3149556
KS
714{
715 register int i;
716
717 i = mesg;
718 if (i < 1 || i > msgCount)
719 panic("Bad message number to unmark");
720 message[i-1].m_flag &= ~MMARK;
721}
722
723/*
724 * Return the message number corresponding to the passed meta character.
725 */
a0d23834 726int
b3149556 727metamess(meta, f)
a0d23834 728 int meta, f;
b3149556
KS
729{
730 register int c, m;
731 register struct message *mp;
732
733 c = meta;
734 switch (c) {
735 case '^':
736 /*
737 * First 'good' message left.
738 */
739 for (mp = &message[0]; mp < &message[msgCount]; mp++)
740 if ((mp->m_flag & MDELETED) == f)
741 return(mp - &message[0] + 1);
742 printf("No applicable messages\n");
743 return(-1);
744
745 case '$':
746 /*
747 * Last 'good message left.
748 */
749 for (mp = &message[msgCount-1]; mp >= &message[0]; mp--)
750 if ((mp->m_flag & MDELETED) == f)
751 return(mp - &message[0] + 1);
752 printf("No applicable messages\n");
753 return(-1);
754
755 case '.':
756 /*
757 * Current message.
758 */
759 m = dot - &message[0] + 1;
760 if ((dot->m_flag & MDELETED) != f) {
761 printf("%d: Inappropriate message\n", m);
762 return(-1);
763 }
764 return(m);
765
766 default:
767 printf("Unknown metachar (%c)\n", c);
768 return(-1);
769 }
770}