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.4 (Berkeley) 05/01/95";
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
getmsglist(buf,vector,flags)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
markall(buf,f)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
evalcol(col)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
check(mesg,f)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
getrawlist(line,argv,argc)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
scan(sp)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
regret(token)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
scaninit()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
first(f,m)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
matchsender(str,mesg)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 passed name received the passed message number. Return true
649 * if so.
650 */
651
652 static char *to_fields[] = { "to", "cc", "bcc", 0 };
653
matchto(str,mesg)654 matchto(str, mesg)
655 char *str;
656 {
657 register struct message *mp;
658 register char *cp, *cp2, *backup, **to;
659
660 str++;
661
662 if (*str == 0) /* null string matches nothing instead of everything */
663 return(0);
664
665 mp = &message[mesg-1];
666
667 for (to = to_fields; *to; to++) {
668 cp = str;
669 cp2 = hfield(*to, mp);
670 if (cp2 != NOSTR) {
671 backup = cp2;
672 while (*cp2) {
673 if (*cp == 0)
674 return(1);
675 if (raise(*cp++) != raise(*cp2++)) {
676 cp2 = ++backup;
677 cp = str;
678 }
679 }
680 if (*cp == 0)
681 return(1);
682 }
683 }
684 return(0);
685 }
686
687 /*
688 * See if the given string matches inside the subject field of the
689 * given message. For the purpose of the scan, we ignore case differences.
690 * If it does, return true. The string search argument is assumed to
691 * have the form "/search-string." If it is of the form "/," we use the
692 * previous search string.
693 */
694
695 char lastscan[128];
696 int
matchsubj(str,mesg)697 matchsubj(str, mesg)
698 char *str;
699 int mesg;
700 {
701 register struct message *mp;
702 register char *cp, *cp2, *backup;
703
704 str++;
705 if (strlen(str) == 0)
706 str = lastscan;
707 else
708 strcpy(lastscan, str);
709 mp = &message[mesg-1];
710
711 /*
712 * Now look, ignoring case, for the word in the string.
713 */
714
715 if (value("searchheaders") && (cp = index(str, ':'))) {
716 /* Check for special case "/To:" */
717 if (raise(str[0]) == 'T' && raise(str[1]) == 'O' &&
718 str[2] == ':')
719 return(matchto(cp, mesg));
720 *cp++ = '\0';
721 cp2 = hfield(*str ? str : "subject", mp);
722 cp[-1] = ':';
723 str = cp;
724 } else {
725 cp = str;
726 cp2 = hfield("subject", mp);
727 }
728 if (cp2 == NOSTR)
729 return(0);
730 backup = cp2;
731 while (*cp2) {
732 if (*cp == 0)
733 return(1);
734 if (raise(*cp++) != raise(*cp2++)) {
735 cp2 = ++backup;
736 cp = str;
737 }
738 }
739 return(*cp == 0);
740 }
741
742 /*
743 * Mark the named message by setting its mark bit.
744 */
745 void
mark(mesg)746 mark(mesg)
747 int mesg;
748 {
749 register int i;
750
751 i = mesg;
752 if (i < 1 || i > msgCount)
753 panic("Bad message number to mark");
754 message[i-1].m_flag |= MMARK;
755 }
756
757 /*
758 * Unmark the named message.
759 */
760 void
unmark(mesg)761 unmark(mesg)
762 int mesg;
763 {
764 register int i;
765
766 i = mesg;
767 if (i < 1 || i > msgCount)
768 panic("Bad message number to unmark");
769 message[i-1].m_flag &= ~MMARK;
770 }
771
772 /*
773 * Return the message number corresponding to the passed meta character.
774 */
775 int
metamess(meta,f)776 metamess(meta, f)
777 int meta, f;
778 {
779 register int c, m;
780 register struct message *mp;
781
782 c = meta;
783 switch (c) {
784 case '^':
785 /*
786 * First 'good' message left.
787 */
788 for (mp = &message[0]; mp < &message[msgCount]; mp++)
789 if ((mp->m_flag & MDELETED) == f)
790 return(mp - &message[0] + 1);
791 printf("No applicable messages\n");
792 return(-1);
793
794 case '$':
795 /*
796 * Last 'good message left.
797 */
798 for (mp = &message[msgCount-1]; mp >= &message[0]; mp--)
799 if ((mp->m_flag & MDELETED) == f)
800 return(mp - &message[0] + 1);
801 printf("No applicable messages\n");
802 return(-1);
803
804 case '.':
805 /*
806 * Current message.
807 */
808 m = dot - &message[0] + 1;
809 if ((dot->m_flag & MDELETED) != f) {
810 printf("%d: Inappropriate message\n", m);
811 return(-1);
812 }
813 return(m);
814
815 default:
816 printf("Unknown metachar (%c)\n", c);
817 return(-1);
818 }
819 }
820