xref: /original-bsd/usr.bin/mail/list.c (revision 7afc0fa3)
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.2 (Berkeley) 04/19/94";
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
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
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
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
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
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
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
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
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
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
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 given string matches inside the subject field of the
649  * given message.  For the purpose of the scan, we ignore case differences.
650  * If it does, return true.  The string search argument is assumed to
651  * have the form "/search-string."  If it is of the form "/," we use the
652  * previous search string.
653  */
654 
655 char lastscan[128];
656 int
657 matchsubj(str, mesg)
658 	char *str;
659 	int mesg;
660 {
661 	register struct message *mp;
662 	register char *cp, *cp2, *backup;
663 
664 	str++;
665 	if (strlen(str) == 0)
666 		str = lastscan;
667 	else
668 		strcpy(lastscan, str);
669 	mp = &message[mesg-1];
670 
671 	/*
672 	 * Now look, ignoring case, for the word in the string.
673 	 */
674 
675 	if (value("searchheaders") && (cp = index(str, ':'))) {
676 		*cp++ = '\0';
677 		cp2 = hfield(str, mp);
678 		cp[-1] = ':';
679 		str = cp;
680 	} else {
681 		cp = str;
682 		cp2 = hfield("subject", mp);
683 	}
684 	if (cp2 == NOSTR)
685 		return(0);
686 	backup = cp2;
687 	while (*cp2) {
688 		if (*cp == 0)
689 			return(1);
690 		if (raise(*cp++) != raise(*cp2++)) {
691 			cp2 = ++backup;
692 			cp = str;
693 		}
694 	}
695 	return(*cp == 0);
696 }
697 
698 /*
699  * Mark the named message by setting its mark bit.
700  */
701 void
702 mark(mesg)
703 	int mesg;
704 {
705 	register int i;
706 
707 	i = mesg;
708 	if (i < 1 || i > msgCount)
709 		panic("Bad message number to mark");
710 	message[i-1].m_flag |= MMARK;
711 }
712 
713 /*
714  * Unmark the named message.
715  */
716 void
717 unmark(mesg)
718 	int mesg;
719 {
720 	register int i;
721 
722 	i = mesg;
723 	if (i < 1 || i > msgCount)
724 		panic("Bad message number to unmark");
725 	message[i-1].m_flag &= ~MMARK;
726 }
727 
728 /*
729  * Return the message number corresponding to the passed meta character.
730  */
731 int
732 metamess(meta, f)
733 	int meta, f;
734 {
735 	register int c, m;
736 	register struct message *mp;
737 
738 	c = meta;
739 	switch (c) {
740 	case '^':
741 		/*
742 		 * First 'good' message left.
743 		 */
744 		for (mp = &message[0]; mp < &message[msgCount]; mp++)
745 			if ((mp->m_flag & MDELETED) == f)
746 				return(mp - &message[0] + 1);
747 		printf("No applicable messages\n");
748 		return(-1);
749 
750 	case '$':
751 		/*
752 		 * Last 'good message left.
753 		 */
754 		for (mp = &message[msgCount-1]; mp >= &message[0]; mp--)
755 			if ((mp->m_flag & MDELETED) == f)
756 				return(mp - &message[0] + 1);
757 		printf("No applicable messages\n");
758 		return(-1);
759 
760 	case '.':
761 		/*
762 		 * Current message.
763 		 */
764 		m = dot - &message[0] + 1;
765 		if ((dot->m_flag & MDELETED) != f) {
766 			printf("%d: Inappropriate message\n", m);
767 			return(-1);
768 		}
769 		return(m);
770 
771 	default:
772 		printf("Unknown metachar (%c)\n", c);
773 		return(-1);
774 	}
775 }
776