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