first cut at new makefile; file reorg, new depend
[unix-history] / usr / src / usr.bin / mail / list.c
CommitLineData
9552e6b8
DF
1/*
2 * Copyright (c) 1980 Regents of the University of California.
0c5f72fb
KB
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms are permitted
acfc7e9b
KB
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.
9552e6b8
DF
16 */
17
acfc7e9b 18#ifndef lint
20878753 19static char sccsid[] = "@(#)list.c 5.13 (Berkeley) %G%";
acfc7e9b 20#endif /* not lint */
b3149556
KS
21
22#include "rcv.h"
23#include <ctype.h>
24
25/*
26 * Mail -- a mail program
27 *
28 * Message list handling.
29 */
30
b3149556
KS
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
38getmsglist(buf, vector, flags)
39 char *buf;
40 int *vector;
41{
42 register int *ip;
43 register struct message *mp;
44
c1241556
EW
45 if (msgCount == 0) {
46 *vector = 0;
47 return 0;
48 }
b3149556
KS
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;
c1241556 55 *ip = 0;
b3149556
KS
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
2919b83d
KS
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
80struct 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
94static int lastcolmod;
95
b3149556
KS
96markall(buf, f)
97 char buf[];
98{
99 register char **np;
100 register int i;
2919b83d 101 register struct message *mp;
b3149556 102 char *namelist[NMLSIZE], *bufp;
2919b83d 103 int tok, beg, mc, star, other, valdot, colmod, colresult;
b3149556
KS
104
105 valdot = dot - &message[0] + 1;
2919b83d 106 colmod = 0;
b3149556
KS
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:
120number:
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++)
20878753 131 if (f == MDELETED || (message[i - 1].m_flag & MDELETED) == 0)
c9c93837 132 mark(i);
b3149556
KS
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 }
9745f5e0
S
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);
b3149556
KS
161 break;
162
163 case TDASH:
164 if (beg == 0) {
9745f5e0
S
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);
b3149556
KS
174 }
175 break;
176
177 case TSTRING:
178 if (beg != 0) {
179 printf("Non-numeric second argument\n");
180 return(-1);
181 }
182 other++;
2919b83d
KS
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);
b3149556
KS
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;
a8a981d5
EW
211
212 case TERROR:
213 return -1;
b3149556
KS
214 }
215 tok = scan(&bufp);
216 }
2919b83d 217 lastcolmod = colmod;
b3149556
KS
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
2919b83d 239 if ((np > namelist || colmod != 0) && mc == 0)
b3149556 240 for (i = 1; i <= msgCount; i++)
9745f5e0 241 if ((message[i-1].m_flag & MDELETED) == f)
b3149556
KS
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++)
f7a4f91c
KS
252 if (**np == '/') {
253 if (matchsubj(*np, i)) {
254 mc++;
255 break;
256 }
257 }
258 else {
a8a981d5 259 if (matchsender(*np, i)) {
f7a4f91c
KS
260 mc++;
261 break;
262 }
b3149556
KS
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 }
2919b83d
KS
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 */
326evalcol(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);
b3149556
KS
335 return(0);
336}
337
338/*
339 * Check the passed message number for legality and proper flags.
20878753
EW
340 * If f is MDELETED, then either kind will do. Otherwise, the message
341 * has to be undeleted.
b3149556 342 */
b3149556
KS
343check(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];
20878753 352 if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
b3149556
KS
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
fee4667a 364getrawlist(line, argv, argc)
b3149556
KS
365 char line[];
366 char **argv;
fee4667a 367 int argc;
b3149556 368{
433e5791
EW
369 register char c, *cp, *cp2, quotec;
370 int argn;
371 char linebuf[BUFSIZ];
b3149556 372
433e5791 373 argn = 0;
b3149556 374 cp = line;
433e5791
EW
375 for (;;) {
376 for (; *cp == ' ' || *cp == '\t'; cp++)
377 ;
378 if (*cp == '\0')
b3149556 379 break;
433e5791
EW
380 if (argn >= argc - 1) {
381 printf(
382 "Too many elements in the list; excess discarded.\n");
fee4667a
S
383 break;
384 }
433e5791
EW
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);
b3149556 446 }
433e5791
EW
447 argv[argn] = NOSTR;
448 return argn;
b3149556
KS
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
458struct 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
473scan(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) {
85d5837f 482 strcpy(lexstring, string_stack[regretp]);
b3149556
KS
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
470c33f3 494 while (c == ' ' || c == '\t')
b3149556
KS
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;
470c33f3 547 if (c == '\'' || c == '"') {
b3149556
KS
548 quotec = c;
549 c = *cp++;
550 }
551 while (c != '\0') {
a8a981d5
EW
552 if (c == quotec) {
553 cp++;
b3149556 554 break;
a8a981d5 555 }
470c33f3 556 if (quotec == 0 && (c == ' ' || c == '\t'))
b3149556
KS
557 break;
558 if (cp2 - lexstring < STRINGLEN-1)
559 *cp2++ = c;
560 c = *cp++;
561 }
a8a981d5 562 if (quotec && c == 0) {
b3149556 563 fprintf(stderr, "Missing %c\n", quotec);
a8a981d5
EW
564 return TERROR;
565 }
b3149556
KS
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
575regret(token)
576{
577 if (++regretp >= REGDEP)
578 panic("Too many regrets");
579 regretstack[regretp] = token;
580 lexstring[STRINGLEN-1] = '\0';
85d5837f 581 string_stack[regretp] = savestr(lexstring);
b3149556
KS
582 numberstack[regretp] = lexnumber;
583}
584
585/*
586 * Reset all the scanner global variables.
587 */
588
589scaninit()
590{
591 regretp = -1;
592}
593
594/*
595 * Find the first message whose flags & m == f and return
596 * its message number.
597 */
598
599first(f, m)
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 */
620
a8a981d5 621matchsender(str, mesg)
b3149556
KS
622 char *str;
623{
9745f5e0 624 register char *cp, *cp2, *backup;
b3149556 625
a8a981d5
EW
626 if (!*str) /* null string matches nothing instead of everything */
627 return 0;
628 backup = cp2 = nameof(&message[mesg - 1], 0);
9745f5e0
S
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);
b3149556
KS
639}
640
f7a4f91c
KS
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
649char lastscan[128];
650
651matchsubj(str, mesg)
652 char *str;
653{
654 register struct message *mp;
312bdff6 655 register char *cp, *cp2, *backup;
f7a4f91c
KS
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);
312bdff6 672 backup = cp2;
f7a4f91c
KS
673 while (*cp2) {
674 if (*cp == 0)
675 return(1);
312bdff6
KS
676 if (raise(*cp++) != raise(*cp2++)) {
677 cp2 = ++backup;
f7a4f91c 678 cp = str;
312bdff6 679 }
f7a4f91c
KS
680 }
681 return(*cp == 0);
682}
683
b3149556
KS
684/*
685 * Mark the named message by setting its mark bit.
686 */
687
688mark(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
702unmark(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
716metamess(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}