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