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