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