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